/*
 * StreamItemSource.java
 *
 * Created on August 20, 2007, 12:38 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.infoset.xml.jaxp;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URI;
import java.net.URISyntaxException;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import org.infoset.xml.Attribute;
import org.infoset.xml.Characters;
import org.infoset.xml.Element;
import org.infoset.xml.ElementEnd;
import org.infoset.xml.Infoset;
import org.infoset.xml.InfosetFactory;
import org.infoset.xml.Item;
import org.infoset.xml.ItemConstructor;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.ItemSource;
import org.infoset.xml.Name;
import org.infoset.xml.XMLException;

/**
 *
 * @author alex
 */
public class StreamItemSource implements ItemSource,XMLStreamConstants
{
   static XMLInputFactory factory = XMLInputFactory.newInstance();

   ItemConstructor constructor = null;
   Infoset infoset = null;
   XMLStreamReader reader;
   URI location;
   Reader input;
   boolean closeOnEnd;
   boolean doNext = false;
   
   /** Creates a new instance of StreamItemSource */
   public StreamItemSource(URI location)
      throws IOException,XMLException
   {
      this(location,new InputStreamReader(location.toURL().openStream(),"UTF-8"),true);
   }
   public StreamItemSource(URI location,Reader input,boolean closeOnEnd)
      throws XMLException
   {
      this.location = location;
      this.input = input;
      this.closeOnEnd = closeOnEnd;
      try {
         this.reader = location==null ? factory.createXMLStreamReader(input) : factory.createXMLStreamReader(location.toString(),input);
      } catch (XMLStreamException ex) {
         throw new XMLException("Cannot get XMLStreamReader instance.",ex);
      }
   }
   
   public void setItemConstructor(ItemConstructor c) {
      constructor = c;
      infoset = c.getInfoset();
   }
   
   public Item receive()
      throws XMLException 
   {
      try {
         if (constructor==null) {
            constructor = InfosetFactory.getDefaultInfoset().createItemConstructor();
            infoset = constructor.getInfoset();
         }
         //System.out.println("Receiving...");
         if (!reader.hasNext()) {
            return null;
         }
         int type = doNext ? reader.next() : reader.getEventType();
         doNext = false;
         while (type==ATTRIBUTE || type==NAMESPACE) {
            type = reader.next();
         }

         //System.out.println("Looking at type "+type);
         Item retval = null;
         switch (type) {
            case START_DOCUMENT:
               retval = constructor.createDocument(location);
               doNext = true;
               break;
            case END_DOCUMENT:
               if (closeOnEnd) {
                  try {
                     input.close();
                  } catch (IOException ex) {
                     throw new XMLException("I/O error on close.",ex);
                  }
               }
               retval = constructor.createDocumentEnd();
               break;
            case START_ELEMENT: {
               try {
                  String lname = reader.getLocalName();
                  String sns = reader.getNamespaceURI();
                  URI ns = sns==null ? Name.NO_NAMESPACE : infoset.createNamespace(sns);
                  Name name = infoset.createName(ns,lname);
                  Element e = constructor.createElement(name);
                  String prefix = reader.getPrefix();
                  e.setPrefix(prefix==null ? Name.NO_PREFIX : prefix);
                  int count = reader.getAttributeCount();
                  for (int i=0; i<count; i++) {
                     lname = reader.getAttributeLocalName(i);
                     sns = reader.getAttributeNamespace(i);
                     prefix = reader.getAttributePrefix(i);
                     ns = sns==null ? Name.NO_NAMESPACE : infoset.createNamespace(sns);
                     name = infoset.createName(ns,lname);
                     Attribute att = e.setAttributeValue(name,reader.getAttributeValue(i));
                     att.setPrefix(prefix);
                  }
                  count = reader.getNamespaceCount();
                  for (int i=0; i<count; i++) {
                     prefix = reader.getNamespacePrefix(i);
                     sns = reader.getNamespaceURI(i);
                     ns = sns==null ? Name.NO_NAMESPACE : infoset.createNamespace(sns);
                     if (prefix==null) {
                        prefix = Name.NO_PREFIX;
                     }
                     e.addNamespaceBinding(prefix,ns);
                  }
                  retval = e;
                  doNext = true;
               } catch (URISyntaxException ex) {
                  throw new XMLException("Invalid URI:"+ex.getMessage(),ex);
               }
            }
               break;
            case END_ELEMENT:
               try {
                  String lname = reader.getLocalName();
                  String sns = reader.getNamespaceURI();
                  URI ns = sns==null ? Name.NO_NAMESPACE : infoset.createNamespace(sns);
                  Name name = infoset.createName(ns,lname);
                  //System.out.println("End: "+name);
                  ElementEnd e = constructor.createElementEnd(name);
                  retval = e;
                  doNext = true;
               } catch (URISyntaxException ex) {
                  throw new XMLException("Invalid URI:"+ex.getMessage(),ex);
               }
               break;
            case CHARACTERS:
               StringBuilder builder = new StringBuilder();
               while (reader.getEventType()==CHARACTERS) {
                  builder.append(reader.getText());
                  reader.next();
               }
               retval = constructor.createCharacters(builder.toString());
               break;
            case SPACE:
               retval = constructor.createCharacters(reader.getText());
               doNext = true;
               break;
            case CDATA:
               Characters c = constructor.createCharacters(reader.getText());
               c.setCDataSection(true);
               retval = c;
               doNext = true;
               break;
            case COMMENT:
               retval = constructor.createComment(reader.getText());
               doNext = true;
               break;
            case PROCESSING_INSTRUCTION:
               retval = constructor.createProcessingInstruction(infoset.createName(reader.getPITarget()),reader.getPIData());
               doNext = true;
               break;
            default:
               throw new XMLException("Unsupported event type "+type);
         }
         //System.out.println("Returing: "+retval);
         return retval;
      } catch (XMLStreamException ex) {
         throw new XMLException(ex.getMessage(),ex.getCause());
      }
   }
   
   /**
    * Flushs the items from the source.
    * @param all If true, all the items to the end of the document will be flushed.  Otherwise, just the current level's children will be flushed.
    */
   public void flush(boolean all)
      throws XMLException
   {
      throw new XMLException("Not implemented");
   }
   
   /**
    * Seeks to a particular element at the current level.
    * @param name The name of the element.
    */
   public Element seek(Name name)
      throws XMLException
   {
      throw new XMLException("Not implemented");
      
   }
   
   /**
    * The type to seek to (e.g. Element.TYPE).
    * @param type The type code to use for the seek.
    * @return An item or null for not found.
    * @throws XMLException Thrown when there is an implementation error.
    */
   public Item seekByType(Item.ItemType type)
      throws XMLException
   {
      throw new XMLException("Not implemented");
      
   }
   
   public void copyTo(ItemDestination dest)
      throws XMLException
   {
      Item item;
      while ((item=receive())!=null) {
         dest.send(item);
      }
   }
   
}
